Skip to content

Immutable folder support in DABs#5254

Open
andrewnester wants to merge 24 commits into
mainfrom
demo-immutable
Open

Immutable folder support in DABs#5254
andrewnester wants to merge 24 commits into
mainfrom
demo-immutable

Conversation

@andrewnester

@andrewnester andrewnester commented May 15, 2026

Copy link
Copy Markdown
Contributor

Changes

Added support for deploying bundles to immutable folders in the workspace

Enabled by using

bundle:
  deployment: 
    immutable_folder: true

Why

Tests

Added an acceptance tests

@andrewnester andrewnester requested a review from pietern May 15, 2026 09:56
@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented May 28, 2026

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: e26f085

Run: 28094333178

Env 🟨​KNOWN 🔄​flaky 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
🟨​ aws linux 7 13 244 1028 5:32
🟨​ aws windows 7 13 246 1026 8:02
💚​ aws-ucws linux 7 13 334 944 5:29
💚​ aws-ucws windows 7 13 336 942 5:40
💚​ azure linux 1 15 247 1026 4:43
💚​ azure windows 1 15 249 1024 5:13
🔄​ azure-ucws linux 2 1 15 337 940 9:00
🔄​ azure-ucws windows 2 1 15 339 938 7:27
💚​ gcp linux 1 15 246 1028 4:15
💚​ gcp windows 1 15 248 1026 4:49
22 interesting tests: 13 SKIP, 7 KNOWN, 2 flaky
Test Name aws linux aws windows aws-ucws linux aws-ucws windows azure linux azure windows azure-ucws linux azure-ucws windows gcp linux gcp windows
🟨​ TestAccept 🟨​K 🟨​K 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/bundle/invariant/no_drift 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/permissions 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions 🟨​K 🟨​K 💚​R 💚​R 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=direct 🟨​K 🟨​K 💚​R 💚​R
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=terraform 🟨​K 🟨​K 💚​R 💚​R
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions 🟨​K 🟨​K 💚​R 💚​R 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=direct 🟨​K 🟨​K 💚​R 💚​R
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=terraform 🟨​K 🟨​K 💚​R 💚​R
🙈​ TestAccept/bundle/resources/postgres_branches/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/recreate 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/replace_existing 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/update_protected 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/without_branch_id 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_endpoints/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_projects/update_display_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/synced_database_tables/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_indexes/recreate/embedding_dimension 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/ssh/connection 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🔄​ TestFetchRepositoryInfoAPI_FromRepo/root ✅​p ✅​p ✅​p ✅​p ✅​p ✅​p 🔄​f 🔄​f ✅​p ✅​p
🔄​ TestFetchRepositoryInfoAPI_FromRepo/subdir ✅​p ✅​p ✅​p ✅​p ✅​p ✅​p 🔄​f 🔄​f ✅​p ✅​p
Top 4 slowest tests (at least 2 minutes):
duration env testname
3:24 azure windows TestAccept
3:17 aws-ucws windows TestAccept
3:15 azure-ucws windows TestAccept
3:12 gcp windows TestAccept

@andrewnester andrewnester marked this pull request as ready for review June 1, 2026 11:34
@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Approval status: pending

/acceptance/bundle/ - needs approval

30 files changed
Suggested: @denik
Also eligible: @pietern, @janniklasrose, @shreyas-goenka, @lennartkats-db, @anton-107

/bundle/ - needs approval

28 files changed
Suggested: @denik
Also eligible: @pietern, @janniklasrose, @shreyas-goenka, @lennartkats-db, @anton-107

/cmd/bundle/ - needs approval

Files: cmd/bundle/run.go, cmd/bundle/utils/process.go
Suggested: @denik
Also eligible: @pietern, @janniklasrose, @shreyas-goenka, @lennartkats-db, @anton-107

/libs/sync/ - needs approval

Files: libs/sync/sync.go
Suggested: @simonfaltum
Also eligible: @Divyansh-db, @tanmay-db, @renaudhartert-db, @parthban-db, @hectorcast-db, @tejaskochar-db, @mihaimitrea-db, @chrisst, @rauchy

General files (require maintainer)

Files: acceptance/bin/print_requests.py, libs/testserver/handlers.go
Based on git history:

  • @denik -- recent work in bundle/direct/, bundle/phases/, bundle/config/mutator/

Any maintainer (@anton-107, @denik, @pietern, @shreyas-goenka, @simonfaltum, @renaudhartert-db) can approve all areas.
See OWNERS for ownership rules.

@andrewnester andrewnester requested a review from denik June 2, 2026 10:01

@shreyas-goenka shreyas-goenka left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comments - other than the bit where we use metadata.json

Comment thread bundle/phases/deploy.go Outdated
@@ -15,6 +15,9 @@ type Bundle struct {

type Workspace struct {
FilePath string `json:"file_path"`
// SnapshotPath is the workspace path of the immutable snapshot uploaded
// during deployment. Only populated for bundles with bundle.immutable = true.
SnapshotPath string `json:"snapshot_path,omitempty"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the UI use the immutable snapshot path? In that case we'll need to add it to DMS as well.

Comment thread bundle/deploy/snapshot/upload.go
Comment thread bundle/deploy/snapshot/client.go
Comment thread bundle/deploy/snapshot/path_test.go Outdated
Comment thread bundle/deploy/metadata/load.go Outdated
Comment thread bundle/config/mutator/translate_paths.go Outdated
Comment thread bundle/config/mutator/resolve_variable_references.go
Comment thread bundle/config/mutator/resourcemutator/process_static_resources.go Outdated
Comment thread acceptance/bundle/deploy/immutable/output.txt Outdated
}

// The real API uses the workspace user UUID (not email) in the snapshot path,
// matching service-principal identities used in cloud acceptance tests.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional: Should we also use UUIDs here for better fidelity? The benefits are minor if we have cloud coverage. The difference being users have CAN_MANAGE always on their home directory but that's likely not true for /Users/userId?

Comment thread bundle/permissions/workspace_root.go Outdated
Comment thread bundle/internal/schema/annotations.yml Outdated
Whether to fail on active runs. If this is set to true a deployment that is running can be interrupted.
"immutable_folder":
"description": |-
Whether to upload bundle files and artifacts as a single immutable snapshot. When true, all files are packaged into a zip and uploaded via the snapshot API, and workspace.file_path and workspace.artifact_path are set to the returned content-addressed path. The validate and plan commands make no mutative API calls when this is enabled.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Snapshots API is internal. We should not refer to this in the docs.

Comment thread bundle/deploy/snapshot/translate_paths.go Outdated
func (m *translateResourcePaths) Name() string { return "snapshot.TranslateResourcePaths" }

func (m *translateResourcePaths) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnostics {
localPrefix := b.SyncRootPath + "/"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe strings.TrimSuffix("/") as well to account for trailing /? Or is that not possible?

Comment thread bundle/deploy/snapshot/state.go Outdated
Comment thread bundle/deploy/snapshot/state.go Outdated

// SaveState writes the snapshot path to the local deployment state directory
// so it can be recovered during destroy without reading metadata.json.
func SaveState() bundle.Mutator {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider integrating this with deployment WAL? If we can record the snapshot upload event we can avoid reuploading it on a subsequent deployment since it already exists.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't really, can we? The deployment WAL calculated before the execution of deployment as part of plan but we build and upload later in the phase

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume during deployments we write to WAL so surely we should be able to do that during / after file upload? Otherwise do how do we capture if the plan was partially applied.

@shreyas-goenka shreyas-goenka Jun 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could be misunderstanding the WAL - I'm not familiar with it (will look into it) - I just assumed it records actions as we do them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I misunderstood you and thought you're talking about execution graph and not WAL :) WAL indeed makes sense but it's currently limitted to only creation of resources. I think it might worth expanding it generally to more steps of deploy but this is outside of the scope

Comment thread acceptance/bundle/deploy/immutable-no-artifacts/output.txt
Comment thread bundle/config/mutator/resourcemutator/process_static_resources.go Outdated
Comment thread acceptance/bundle/deploy/immutable/test.toml Outdated
}

func (m *overrideImmutableFolder) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
if env.Get(ctx, "DATABRICKS_IMMUTABLE_FOLDER") == "" {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For later: move to bundle/env to centralize all env vars we use, and infix with _BUNDLE_.

Comment thread bundle/config/mutator/override_immutable_folder_test.go Outdated
state.Files = fl

// Persist the snapshot path so destroy on a different machine can find it.
state.SnapshotPath = b.Config.Workspace.SnapshotPath

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This state file is not needed for destroy today because we remove the file path recursively.

Storing a different path means there is proper state to keep track of.

Can this be implemented as a (hidden?) direct engine resource instead? Then we keep track of it next to the other state, benefit from the WAL, can auto-resolve paths on deployment, etc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, this might be useful. The only limitation I see it is that this will be only available for direct engine while now it's both.
Also I believe we don't strictly need this in the version revision of the feature and can follow up with this later

Comment thread libs/sync/sync.go
}
return s.GetFileList(ctx)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also have a "GetSyncOptions" somewhere. Can we reuse that to get to the file list without duplicating?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed here: 3a0ec41

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be removed if no longer used.


hint: Packages were unavailable because the network was disabled. When
the network is disabled, registry packages may only be read from the
cache.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this supposed to fail?

Comment thread bundle/config/mutator/resolve_variable_references.go
Comment thread bundle/phases/deploy.go Outdated
return
}

if b.Config.Experimental == nil || !b.Config.Experimental.ImmutableFolder {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Collapse into a single var at the top of this function so we don't need to repeat it and two modes are clear.

Comment thread bundle/phases/destroy.go
Comment thread bundle/phases/destroy.go Outdated
// Allows running the full test suite against the immutable folder code path without
// modifying any databricks.yml files.
mutator.OverrideImmutableFolder(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this test only?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is only used for test

Comment thread libs/sync/sync.go
}
return s.GetFileList(ctx)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be removed if no longer used.

})
}
return acl
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: can live in a different file.

Comment thread bundle/deploy/ensure_deployment_id.go Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants